package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/namsral/flag"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/health"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"

	consulpb "mxshop_srvs/consul_srv/proto/gen/v1"
	goodspb "mxshop_srvs/goods_srv/proto/gen/v1"
	inventorypb "mxshop_srvs/inventory_srv/proto/gen/v1"
	"mxshop_srvs/order_srv/client/order"
	"mxshop_srvs/order_srv/dao/mysql"
	"mxshop_srvs/order_srv/handler"
	"mxshop_srvs/order_srv/id"
	"mxshop_srvs/order_srv/logger"
	orderpb "mxshop_srvs/order_srv/proto/gen/v1"
	"mxshop_srvs/order_srv/setting"
	"mxshop_srvs/order_srv/utils"
	"mxshop_srvs/order_srv/utils/consul"
)

var IP = flag.String("ip", "0.0.0.0", "ip地址")
var Port = flag.Int("port", 0, "端口号")

func main() {
	flag.Parse()

	// init config
	setting.Init()

	// init logger
	logger.Init()
	defer func(l *zap.Logger) {
		_ = l.Sync()
	}(zap.L())

	// init mysql
	db, err := mysql.Init()
	if err != nil {
		panic(err)
	}

	// init goods service
	goodsConn, err := utils.GetServiceConn(utils.ConsulConfig{
		Host: setting.Conf.ConsulConfig.Host,
		Port: setting.Conf.ConsulConfig.Port,
	}, setting.Conf.GoodsSrvConfig.Name)
	if err != nil {
		zap.L().Fatal("cannot connect goods service", zap.Error(err))
	}
	goodsService := goodspb.NewGoodsClient(goodsConn)

	// init inventory service
	inventoryConn, err := utils.GetServiceConn(utils.ConsulConfig{
		Host: setting.Conf.ConsulConfig.Host,
		Port: setting.Conf.ConsulConfig.Port,
	}, setting.Conf.InventorySrvConfig.Name)
	if err != nil {
		zap.L().Fatal("cannot connect inventory service", zap.Error(err))
	}
	inventoryService := inventorypb.NewInventoryClient(inventoryConn)

	// init consul watch service
	consulConn, err := GetConsulServiceConn()
	if err != nil {
		zap.L().Fatal("cannot connect consul watch service", zap.Error(err))
	}
	consulService := consulpb.NewServiceClient(consulConn)

	b, err := utils.GetOutboundIP()
	if err != nil {
		zap.L().Fatal("get out bound ip failed", zap.Error(err))
	}
	ip := b.String()

	if *Port == 0 {
		port, err := utils.GetFreePort()
		if err != nil {
			zap.L().Fatal("cannot get free port", zap.Error(err))
		}
		*Port = port
	}

	// 获取workerID
	serviceID := fmt.Sprintf("%s-%s-%d", setting.Conf.ConsulConfig.Name, ip, *Port)
	resp, err := consulService.GetWorkerId(context.Background(), &consulpb.ServiceEntity{ServiceId: serviceID})
	if err != nil {
		zap.L().Fatal("failed to get worker id", zap.Error(err))
	}
	_ = consulConn.Close() // 获取完后关闭连接,直到程序退出时再重新连接归还workerID
	workerID := resp.WorkerId
	zap.L().Info("workerID", zap.Int64("workerID", workerID))

	// 创建ID生成器
	IDGenerator, err := id.NewSnowflakeIDGen(workerID)
	if err != nil {
		zap.L().Fatal("cannot new snowflake id generator", zap.Error(err))
	}

	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
	if err != nil {
		zap.L().Fatal("order service cannot listen", zap.Error(err))
	}

	s := grpc.NewServer()
	orderpb.RegisterOrderServiceServer(s, handler.NewService(
		order.NewManager(goodsService, inventoryService),
		mysql.NewGorm(db),
		IDGenerator,
	))

	// gRPC健康检查
	healthpb.RegisterHealthServer(s, health.NewServer())

	// 服务注册
	consulConf := setting.Conf.ConsulConfig
	c, err := consul.NewConsul(fmt.Sprintf("%s:%d", consulConf.Host, consulConf.Port))
	if err != nil {
		zap.L().Fatal("cannot create consul api client", zap.Error(err))
	}

	name := consulConf.Name
	tags := append(consulConf.Tags, setting.Conf.Name)

	err = c.RegisterService(name, ip, *Port, tags)
	if err != nil {
		zap.L().Fatal("register to consul failed", zap.Error(err))
	}

	// 启动服务
	go func() {
		zap.L().Info("server started", zap.String(setting.Conf.Name, fmt.Sprintf("%s:%d", *IP, *Port)))
		err = s.Serve(lis)
		if err != nil {
			zap.L().Fatal("failed to start grpc:", zap.Error(err))
			return
		}
	}()

	quitChan := make(chan os.Signal, 1) // 定义一个通道 当程序退出时从consul中注销服务
	// SIGTERM: kill命令 = kill -15 命令
	// SIGINT: ctrl+c
	// SIGKILL: kill -9 命令
	signal.Notify(quitChan, syscall.SIGTERM, syscall.SIGINT)
	<-quitChan // hang住

	// 程序退出时从consul watch service中归还workerID
	consulConn, err = GetConsulServiceConn()
	if err != nil {
		zap.L().Fatal("cannot connect consul watch service", zap.Error(err))
	}
	consulService = consulpb.NewServiceClient(consulConn)
	_, err = consulService.ReturnWorkerId(context.Background(), &consulpb.ServiceEntity{ServiceId: serviceID})
	if err != nil {
		zap.L().Error("cannot return worker id", zap.String("service_id", serviceID), zap.Error(err))
	}
	_ = consulConn.Close()

	// 程序退出时从consul中注销服务
	err = c.DeregisterService(serviceID)
	if err != nil {
		zap.L().Error("cannot deregister from consul", zap.String("service_id", serviceID), zap.Error(err))
	}
}

func GetConsulServiceConn() (*grpc.ClientConn, error) {
	consulConn, err := utils.GetServiceConn(utils.ConsulConfig{
		Host: setting.Conf.ConsulConfig.Host,
		Port: setting.Conf.ConsulConfig.Port,
	}, setting.Conf.ConsulSrvConfig.Name)
	if err != nil {
		return nil, err
	}
	return consulConn, nil
}
